/*
 * Snake.java
 *
 * Created on January 11, 2007, 7:49 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package net.sf.iSnake.core;

import java.io.Serializable;
import java.util.Iterator;
import java.util.Vector;
import net.sf.iSnake.configuration.iSnakeConfiguration;

/**
 *
 * @author thelinuxmaniac
 * @TODO Implement possible static class for FoodGenerator,iSnakeConfiguration,GameController,etc classes so that only one instance
 * of these class is utilized all over the game
 *
 * @TODO Check if new coordinate is VALID using the position validator in Line No 92
 */
public class Snake implements Serializable, Cloneable  {
    
    private iSnakeConfiguration conf;               // Store the reference to the application configuration
    private GameController gameController;          // Stores the reference to the Game Controller for the game
    
    private Short player_id;                        // Unique id assigned to each player. player_id = 1 is reserved for iSnake
    private Short snakeHeadId;                      // Unique id for snake's head for this player
    private Short snakeTailId;                      // Unique id for snake's tail for this player

    private GameAreaCoordinate head;                // The GameAreaCoordinate representing the head of snake in the GameField
    private GameAreaCoordinate snakeStartPosition;  // starting pos. of snake (sent by server)
    
    // @TODO Change this for optimization
    private Vector tails;                          // Collection of GameAreaCoordinates that represents the tails of snake
    
    private GameAreaCoordinate currentFood;         // The GameAreaCoordinate representing the currentFood of the game
    private GameAreaCoordinate nextFood;            // The GameAreaCoordinate representing the nextFood of the game
    
    private SnakeInputHandler snakeInputHandler;    // The input handler that controls the movement of this snake
    
    private long score;                             // Stores the score of the snake
    
    private Short dx;                               // Represents the change in x coordinate of snake. Used for controlling movement of snake
    private Short dy;                               // Represents the change in y coordinate of snake. Used for controlling movement of snake
    
    private PlayerPreferences playerPreferences;    // Stores the player preferences which is sent in the presession of game to the game server
    
    /**
     * Creates an instance of Snake
     * @param conf The application configuration
     * @param gc GameController for this game
     */
    
    public Snake()  {
        this.dx = new Short("0");
        this.dy = new Short("0");
        this.score = new Short("0");
        this.player_id=new Short("-1");
        this.snakeHeadId = new Short("-1");
        this.snakeTailId = new Short("-1");
        
        
        tails = new Vector(1,1);
        this.setHead(new GameAreaCoordinate(-1, -1));
        this.setSnakeStartPosition(new GameAreaCoordinate(-1,-1));
        this.currentFood = new GameAreaCoordinate(-1,-1);
        this.nextFood = new GameAreaCoordinate(-1,-1);
    }
    
    public Snake(iSnakeConfiguration conf,GameController gc) {
        this.setConf(conf);
        this.gameController = gc;

        this.dx = new Short("0");
        this.dy = new Short("0");
        this.score = new Short("0");
        this.player_id=new Short("-1");
        this.snakeHeadId = new Short("-1");
        this.snakeTailId = new Short("-1");
        
        
        setTails(new Vector(1, 1));
        this.setHead(new GameAreaCoordinate(-1, -1));
        this.setSnakeStartPosition(new GameAreaCoordinate(-1,-1));
        this.currentFood = new GameAreaCoordinate(-1,-1);
        this.nextFood = new GameAreaCoordinate(-1,-1);
    }
    
    // used when retriving object from vector
    public Snake(int player_id)   {
        this.player_id = new Integer(player_id).shortValue();
    }
    
    // used when retriving object from vector
    public Snake(Short player_id)   {
        this.player_id = player_id;
    }
    
    public Snake clone() throws CloneNotSupportedException   {
        Snake clonedS = (Snake) super.clone();
        clonedS.currentFood = currentFood.clone();
        clonedS.head = head.clone();
        clonedS.snakeStartPosition = snakeStartPosition.clone();
        clonedS.nextFood = nextFood.clone();
        clonedS.playerPreferences = (PlayerPreferences) playerPreferences.clone();
        clonedS.tails = (Vector) tails.clone();
        
        clonedS.gameController = gameController;
        clonedS.conf = conf;
        
        return clonedS;
    }
    
    public iSnakeConfiguration getConf() {
        return conf;
    }

    public void setConf(iSnakeConfiguration conf) {
        this.conf = conf;
    }

    public Short getPlayer_id() {
        return player_id;
    }

    /**
     * Sets the player_id and Ids of various snake parts according to the numbering scheme describe below:
     * Suppose a player is assigned a player_id = 3, then its
     * snakeHeadId = 3*10 + 0 = 30
     * snakeTailId = 3*10 + 1 = 31
     * ............. and so on upto 39 as the number space 30 to 39 is reserved for the player with player_id = 3
     *
     * NOTE: Refer to GameController documentation for details on this numbering scheme
     */
    
    public void setPlayer_id(Short player_id) {
        this.player_id = player_id;
        this.snakeHeadId = new Short(new Integer(player_id*10 + gameController.INDEX_SNAKE_HEAD).shortValue());
        this.snakeTailId = new Short(new Integer(player_id*10 + gameController.INDEX_SNAKE_TAIL).shortValue());
        System.out.println("SNAKE details: player_id="+player_id+" head_id="+snakeHeadId+" tail_id="+snakeTailId);
    }
    
    public void setPlayer_id(int pid) {
        short player_id = new Integer(pid).shortValue();
        this.player_id = player_id;
        this.snakeHeadId = new Short(new Integer(player_id*10 + gameController.INDEX_SNAKE_HEAD).shortValue());
        this.snakeTailId = new Short(new Integer(player_id*10 + gameController.INDEX_SNAKE_TAIL).shortValue());
//        System.out.println("SNAKE details: player_id="+player_id+" head_id="+snakeHeadId+" tail_id="+snakeTailId);
    }


    public GameAreaCoordinate getHead() {
        return head;
    }

    public void setHead(GameAreaCoordinate head) {
        this.head = head;
    }
    
    /**
     * Peforms all the internal setup that is required to move snake to next position in game field along with its tails
     * It automatically invoked the writeSnakeToGameField() method if the argument "commitMoveToGameFieldMatrix" is true
     * @param commitMoveToGameFieldMatrix A boolean value indicating whenther writeSnakeToGameField() method will be automatically
     * invoked by this method or not
     */
    public void moveSnake(boolean commitMoveToGameFieldMatrix) {
//        this.dx = snakeInputHandler.getDX();
//        this.dy = snakeInputHandler.getDY();

//        System.out.println("moveSnake called. dx= "+dx+" dy= "+dy);
//        System.out.println("tail size= "+tails.size());
        
        /**
         * Steps involved in updating the coordinates are:
         *  1.  reset the cell containing the last tail of snake in the GameFieldMatrix
         *  2.  remove the last tail
         *  3.  insert a tail at the first position whose coordinate equals the head's coordinate
         *  4.  update the head coordinate with the new values of dx and dy
         */
         if(getTails().isEmpty())    {
            // erase old head
            //@TODO implement a generic method
            resetHeadPositionInGameFieldMatrix();       // clear the old position of head
        }
        else    {
            GameAreaCoordinate newTail = new GameAreaCoordinate();
            try {
                newTail = getHead().clone();
            } catch (CloneNotSupportedException ex) {
                ex.printStackTrace();
            }
//            System.out.println("old tails vector = "+tails.toString());
            resetLastTailPositionInGameFieldMatrix();           // 1 Clear the trail of last tail of snake
            tails.removeElementAt(getTails().size()-1);                // 2
            
            tails.insertElementAt(newTail,0);                      // 3            
//            System.out.println("new tails vector = "+tails.toString());
        }
//        resetHeadPositionInGameFieldMatrix();       // clear the old position of head
        getHead().makeSmallChange(this.dx,this.dy);                  // 4
//        System.out.println("new head coordinate= "+head.toString());
        
        if(commitMoveToGameFieldMatrix)
            writeSnakeToGameField();
    }

    /**
     * Performs the task of writing the coordinates of snake head and it's tail to the GameFieldMatrix
     * The display module will then handle the task of display the new position of snake.
     */
    public void writeSnakeToGameField() {
        gameController.setObjectToGameArea(this.snakeHeadId, getHead());
        for(Iterator itr = getTails().iterator();itr.hasNext();) {
            gameController.setObjectToGameArea(this.snakeTailId, (GameAreaCoordinate) itr.next());
        }

    }
    
    public void resetLastTailPositionInGameFieldMatrix() {
        gameController.resetGameFieldMatrixContents((GameAreaCoordinate) getTails().get(getTails().size()-1));
    }
    
    public void resetHeadPositionInGameFieldMatrix()    {
        gameController.resetGameFieldMatrixContents(getHead());
    }

    /**
     * Adds a new tail to the snake body
     */
    public void addNewTail()    {
        GameAreaCoordinate newTailCoordinate = new GameAreaCoordinate();
        if(getTails().isEmpty())   {
            /**
             * The Snake tail is added to the just next position which is in direction opposite to the movement of snake
             */
            newTailCoordinate.set(this.getHead());
            newTailCoordinate.makeSmallChange((short)(-this.getDx().shortValue()),(short) (-this.getDy().shortValue()));
            getTails().add(getTails().size(),newTailCoordinate);
        }
        else    {
            /**
             * The Snake tail is added to the end of the existing tails
             * NOTE: Vector elements index start from 0 and tails.size() returns no. of elements in vector
             */
            newTailCoordinate.set((GameAreaCoordinate) getTails().get(getTails().size()-1));
            newTailCoordinate.makeSmallChange((short)(-this.getDx().shortValue()),(short) (-this.getDy().shortValue()));
            getTails().add(getTails().size(),newTailCoordinate);
        }
        
    }
    
    public void resetSnakeTails()    {
        gameController.resetGameFieldMatrixContents(tails);
        tails.removeAllElements();
    }
    
    /**
     * Returns the coordinate of the Current Food
     */
    public GameAreaCoordinate getCurrentFood() {
        return currentFood;
    }
    
    /**
     * Sets the coordinate for Current Food
     */
    public void setCurrentFood(GameAreaCoordinate currentFood) {
        this.currentFood = currentFood;
    }

    /**
     * Returns the coordinate of the Next Food
     */
    public GameAreaCoordinate getNextFood() {
        return nextFood;
    }

    /**
     * Returns the coordinate of the Next Food
     */
    public void setNextFood(GameAreaCoordinate nextFood) {
        this.nextFood = nextFood;
    }

    public long getScore() {
        return score;
    }

    public void setScore(long score) {
        this.score = score;
    }

    public Short getDx() {
        return dx;
    }

    public void setDx(Short dx) {
        this.dx = dx;
    }

    public Short getDy() {
        return dy;
    }

    public void setDy(Short dy) {
        this.dy = dy;
    }

    public PlayerPreferences getPlayerPreferences() {
        return playerPreferences;
    }

    public void setPlayerPreferences(PlayerPreferences playerPreferences) {
        this.playerPreferences = playerPreferences;
    }

    public SnakeInputHandler getSnakeInputHandler() {
        return snakeInputHandler;
    }

    public void setSnakeInputHandler(SnakeInputHandler snakeInputHandler) {
        this.snakeInputHandler = snakeInputHandler;
    }

    public Short getSnakeTailId() {
        return snakeTailId;
    }

    public void setSnakeTailId(Short snakeTailId) {
        this.snakeTailId = snakeTailId;
    }

    public Short getSnakeHeadId() {
        return snakeHeadId;
    }

    public void setSnakeHeadId(Short snakeHeadId) {
        this.snakeHeadId = snakeHeadId;
    }

    public void setDxDy(short dx,short dy) {
        this.setDx(new Short(dx));
        this.setDy(new Short(dy));
    }

    public Vector getTails() {
        return tails;
    }

    public void setTails(Vector tails) {
        this.tails = tails;
    }
/*
    public String toString()   {
         return "[player_id="+getPlayer_id()+", name="+getPlayerPreferences().getName()+", head="+getHead().toString()+"]";
         
     }
 */
    public boolean equals(Object obj) {
        Snake s = (Snake) obj;
        if(s.getPlayer_id().equals(this.getPlayer_id()))
            return true;
        else
            return false;
    }

    public GameAreaCoordinate getSnakeStartPosition() {
        try {
            return (GameAreaCoordinate)snakeStartPosition.clone();
        } catch (CloneNotSupportedException ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public void setSnakeStartPosition(GameAreaCoordinate snakeStartPosition) {
        this.snakeStartPosition = snakeStartPosition;
    }
}
